home *** CD-ROM | disk | FTP | other *** search
/ Turnbull China Bikeride / Turnbull China Bikeride - Disc 1.iso / ARGONET / PD / GAMES / WIMPGAME / MINES2.ZIP / !Mines / Doku < prev    next >
Text File  |  1995-02-03  |  9KB  |  203 lines

  1. Hallo Michael,
  2.  
  3. ich habe mit erlaubt, Deinen Code etwas zu ergänzen: es hat mich zu
  4. sehr geärgert, daß man immer nur einen Hint bekommt, obwohl man doch
  5. mehrere brauchte. Und da kam ich auf die Idee eine Routine zu
  6. schreiben, die erst mal prüft, ob der Spieler überhaupt einen Hint
  7. braucht, ihm das mitteilt und ggf. auch gewährt. Um den Zufall gänzlich
  8. auszuschalten, kann der Spieler dann selbst bestimmen, welches Feld
  9. er identifiziert haben möchte. Ich habe diesen Text in folgende Teile
  10. gegliedert: Der Algorithmis, Optimieren, das Endspiel und was ich im
  11. Sourcecode alles verändert habe.
  12.  
  13. Der Algorithmus 
  14. ---------------
  15.  
  16. ist im Prinzip ganz primitiv:
  17. - Suche alle Randpunkte
  18. - Erzeuge alle Belegungen ( für alle Felder Marke/keine Marke )
  19.   das sind 2^anz Stück
  20. - überprüfe alle Belegungen, ob sie keinem aufgedeckten Feld widersprechen,
  21.   d.h. ein Feld mit 3 Nachbarminen 4 oder 2 oder so Marken hat. Wenn
  22.   ja, lösche diese Belegungen
  23. - schaue nach, ob bei allen übrigen Belegungen ein Feld immer eine Mine 
  24.   hat oder immer leer ist. Gibt es ein solches Feld, ist diese Belegung
  25.   zwingend und kann durch Schußfolgern gefunden werden. 
  26.   Der Spieler braucht dann keinen Tip.
  27.  
  28. Das habe ich folgendermaßen impementiert ( wenn Du das im Code mitverfolgen
  29. willst, sei gewarnt: durch die Optimierungen sieht alles anders aus, als ich
  30. es hier beschreibe ). 
  31.  
  32. Das Array border_arr enthält die Randfelder. Die maximale Zahl der Randfelder
  33. ist die maximale Zahl der Felder im Spielfeld ( eingendlich ein paar weniger,
  34. denn wenn kein Feld aufgedeckt ist, gibt es auch keine Randfelder ). Also
  35. wird ein entsprechend großer Speicherblock allokiert. border_anz zeigt auf
  36. das Ende des Arrays, d.h. hinter das letzte belegte Element. Das Aufsammeln
  37. der Randfelder geschieht in einer Schleife in init_border durch find_next.
  38.  
  39. Nachdem die Randfelder alle in diesem Array sind, wird die Rekursions-
  40. routine permutate mit einem Pointer auf das border_arr aufgerufen. Sie 
  41. belegt das Feld zuerst nicht, ruft sich selber mit dem Zeiger auf das nächste
  42. Feld auf, setzt dann die Marke und ruft sich ein zweites Mal auf. Ist der
  43. Zeiger gleich border_anz, d.h. am Ende angekommen, wird die Belegung
  44. überprüft, und wenn sie möglich ist ein p_POSSIBLE, sonst ein p_IMPOSSIBLE
  45. zurückgegeben.
  46.  
  47. Nun muß man nicht tatsächlich alle Belegungen speichern. Es reicht zu wissen,
  48. ob ein Feld immer mit einer Mine belegt war, oder nie, oder ob beides vorkam.
  49. Drei Möglichkeiten kann man in zwei Bits kodieren. Ich habe dazu die Bits 6
  50. und 7 im feld benutzt. Es gibt auch noch einen vierten Initialisierungs-
  51. zustand. Wenn also eine Belegung ohne Marke möglich war, dann wird das mit
  52. einem h_NOMINE vermerkt, es sei denn, das Feld hat schon ein h_MINE oder ein
  53. h_UNKNOWN ( dann gab es schon eine Belegung, bei der dieses Feld eine Marke
  54. hatte, und folglich ist beides möglich ), dann wird es h_UNKNOWN.
  55. Entsprechend, wenn eine Belegung mit eine Marke auf dem Feld möglich ist.
  56.  
  57. Dieser Algorithmus hat nur ein Problem: die Anzahl der Belegungen, die er 
  58. durchprobieren muß, steigt exponentiell mit der Anzahl der Randfelder. Und 
  59. da geht auch der schnellste Computer irgendwann ( in der Praxis recht 
  60. schnell ) in die Knie. Das Zauberwort heißt oprimieren.
  61.  
  62. O P T I M I E R E N   
  63. -------------------
  64.  
  65. 0. Die trivialen Fälle abprüfen: Ein Fehler bringt natürlich auch meine
  66. so sorgsam optimierten Routinen durcheinander. Deswegen wird der
  67. Spieler erst mal auf einen Fehler hingewiesen, wenn es einen gibt.
  68. Dann das einfache abzählen: Wenn ein Feld genau so viele verdeckte
  69. Nachbarfelder hat wie Minen, muß überall eine Marke drauf; wenn ein
  70. Feld soviele Marken hat wie Minen, müssen alle verdeckten Felder leer
  71. sein. Diese Sachen gehen sehr schnell zu entscheiden und ersparen
  72. Mühsameres.
  73.  
  74. 1. Man kann die Belegungen schon beim Erzeugen auf ihre Gültigkeit
  75. prüfen und entsprechend aussortieren. Das geht folgendermaßen: beim
  76. Erstellen der Liste der Randpunkte wird für alle Nachbarfelder (
  77. die möglicherweise offen sind ) mitgezählt, wieviele Felder in ihrer
  78. Nachbarschaft später in der Liste stehen, d.h. noch verändert werden
  79. können ( das macht find_testmode ). Die Routine test_if_possible zählt
  80. nun für alle offenen Nachbarfelder die benachbarten Marken und Minen.
  81. Wenn es mehr Marken als Minen gibt ist sowiso Ende. Wenn aber die
  82. Summe der Marken und der noch veränderbaren Felder kleiner ist als die
  83. Anzahl der Minen, ist auch Ende, weil, selbst wenn alle folgenden
  84. Felder mit Marken belegt werden, immer zuwenig Marken da sind.
  85.  
  86. Diese Vorgehensweise hat noch einen zweiten Vorteil: Wenn alle Felder
  87. der Liste belegt wurden, und nicht schon vorher nach obiger Methode
  88. abgebrochen wurde, ist die Belegung auch gültig, d.h. man muß die
  89. Gültigkeit nicht mehr explizit nachprüfen ( ein formaler Beweis steht
  90. noch aus ... ).
  91.  
  92. 2. Es gibt unabhängige Bereiche ( domains ), die getrennt bearbeitet
  93. werden können. Beispiel:
  94.  
  95. -------------------------
  96. |   |   |   |   |   |   |
  97. -------------------------
  98. |   |R1 | * | * | * |R2 |  
  99. -------------------------
  100. |   |R3 | 3 | 3 | 2 |R4 |
  101. -------------------------
  102. |   |R5 | 1 |   |   |   |
  103. -------------------------
  104.  
  105. Die Wahl von R1,3,5 beeinfußt R2,4 nicht. Und warum ? Weil sie kein
  106. gemeinsames, offenes Nachbarfeld haben. Nach diesem Kriterium kann man
  107. die Liste der Randfelder dann in mehrere Bereiche sortieren ( meistens
  108. ). Das wird von der Routine find_domains erledigt. Der Gewinn ist
  109. mitunter enorm: statt bei 20 Feldern 2^20=1048576 Kombinationen durch-
  110. zuackern, sind vielleicht nur 2*10 Felder 2*2^10=2048 Kombinationen zu
  111. berechnen !
  112. Aber Achtung ! Es schleicht sich auch ein Fehler ein: es gibt nur eine
  113. Begrenzte Zahl an Marken, die man noch vergeben kann. Und besonders im
  114. Endspiel gelingt es dadurch, Belegungen auszuschließen und
  115. Schlußfolgerungen zu machen. Dieser Themenkomplex wird ausführlich im
  116. Abschnitt "Das Endspiel" erörtert.
  117.  
  118. 3. Ab und zu kommt es vor, daß zu mehreren, ähnlichen Situationen eine
  119. Hilfe verlangt wird. Und vielleicht hat sich ein Bereich vom letzten
  120. Mal überhaupt nicht verändert und braucht auch nicht nochmal
  121. bearbeitet zu werden. Dazu ist es nötig, die Bereiche zu speichern und
  122. beim nächsten Mal mit den neu erzeugten zu vergleichen. Finden sich
  123. gleiche Bereiche, kann auf die bereits berechneten Ergebnisse
  124. zurückgeriffen werden.
  125.  
  126. 4. Diese Optimierung ist keine algorithmische, aber kein Freak würde
  127. sie unversucht lassen: die Umsetzung der zeitkritischen Routinen in
  128. Assembler, so geschehen mit test_if_possible. Geschwindigkeitsgewinn: 
  129. Faktor 2.33.
  130.  
  131. Das Endspiel
  132. ------------
  133.  
  134. beginnt dann, wenn durch die kleine Zahl der zur Verfügung stehenden
  135. Marken die möglichen Belegungen eingeschränkt werden. Und wie stellt
  136. man das fest ? Ganz einfach: Für jeden Bereich wird ermittelt,
  137. wieviele Marken er mindestens und höchstens braucht. Ist die Summe der
  138. Höchstwerte aller Bereiche größer als die Anzahl der Marken, haben
  139. sich unmögliche Belegungen eingeschlichen. Was tun ?
  140.  
  141. Die einzige sichere Methode ist, die Sache mit den Bereichen wieder zu
  142. vergessen und alles wie einen einzigen Bereich zu behandeln. Die
  143. Prüfung der Belegung wird um die Begenzung auf die vorhandenen Marken
  144. erweitert.
  145.  
  146. Es gibt noch etwas, über das man nachdenken muß: Die Felder, die nicht
  147. zum Rand gehören. Es kann nämlich sein, daß sich aus den Minimal- bzw.
  148. Maximal-Werten ergibt, daß alle diese Felder Marken tragen bzw. leer
  149. sind. Es gibt folgende Möglichkeiten:
  150.  
  151. mines_left - max_mines > rest_felder => Fehler: Es gibt mehr Marken,
  152. als verteilt werden können.
  153.  
  154. mines_left - max_mines = rest_felder => alle Restfelder haben eine
  155. Marke.
  156.  
  157. mines_left - min_mines = 0 => alle Restfelder sind leer
  158.  
  159. Was ich im Source-Code alles verändert habe
  160. -------------------------------------------
  161.  
  162. 1. Die Reihenfolge der Offsets ofs. Das war nicht zwingend nötig, war
  163. aber einfacher für find_testmode.
  164.  
  165. 2. Ich habe das Shaden des Menüeintrags "Hint" verändert: er ist das
  166. ganze Spiel über anwählbar, nur nach dem Ende wird er geshadet.
  167.  
  168. 3. Die Fenster für kein Tip nötig und Feld aufdecken habe ich in das
  169. Template-File aufgenommen und die zugehörigen Handler werden in hint_init
  170. installiert, das in main_init aufgerufen wird.
  171.  
  172. 4. Ich habe ein Fragezeichen-Sprite erfunden, in die Spritedatei
  173. eingefügt und einen Pointer namens questionmark initialisiert ( in
  174. create_sprite )
  175.  
  176. 5. Zur einfacheren Sprite-Ausgabe schrieb ich die Funktion
  177. draw_sprite und habe überall, wo es sinnvoll war, diesen Aufruf
  178. eingesetzt.
  179.  
  180. 6. Um den Aufbau modularer zu machen, habe ich alle globalen #defines,
  181. Datenstrukturen ... in mineheader.h kopiert. Alle öffentlichen
  182. Funktionen von mines.c bzw. hint.c als Prototyp in mines.h bzw. hint.h
  183. eingetragen.
  184.  
  185. 7. Die einfache Win-Funktion habe ich etwas erweitert: Es gibt nun ein
  186. Fenster, das die Anzahl der unötigen Tips anzeigt ( als Motivation,
  187. nicht immer gleich einen Tip zu verlangen ) und zwei Buttons für
  188. neues Spiel und Ende enthält. Das diese Aktionen nun nicht nur vom
  189. Menü-Handler benutzt werden, habe ich sie in eigene Funktionen
  190. gesetzt. Dort wird auch das Win-Fenster wieder geschlossen ( wenn es
  191. überhaupt auf war ).
  192.  
  193.  
  194. Und nachdem ich mich nun so sehr reingehängt habe, würde ich mich
  195. glücklich schätzen, wenn Du dieses Programm als neue Version wieder
  196. veröffentlichen würdest und meinen Namen irgendwo vermerktest.
  197.  
  198.  
  199. Gunter Blache
  200. Auf dem Wasen 36 
  201. 71640 Ludwigsburg
  202. blachegr@tick.informatik.uni-stuttgart.de
  203.